﻿//++
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
// Module Name:
//
//    ChannelControl.xaml.cs
//
// Abstract:
//    
//    Implements a WPF user control to list the available channels for Enhanced Reminders.
//
//--

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace Microsoft.EnhancedReminders.OutlookAddIn
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class ChannelControl : UserControl, INotifyPropertyChanged
    {
        /// <summary>
        /// Command handler for "Add" button.
        /// </summary>
        public readonly static RoutedCommand AddCommand = new RoutedCommand();
        
        /// <summary>
        /// Command handler for "Edit" button.
        /// </summary>
        public readonly static RoutedCommand EditCommand = new RoutedCommand();
        
        /// <summary>
        /// Command handler for "Delete" button.
        /// </summary>
        public readonly static RoutedCommand DeleteCommand = new RoutedCommand();
        
        /// <summary>
        /// Command handler for "Up" button.
        /// </summary>
        public readonly static RoutedCommand UpCommand = new RoutedCommand();
        
        /// <summary>
        /// Command handler for "Down" button.
        /// </summary>
        public readonly static RoutedCommand DownCommand = new RoutedCommand();

        /// <summary>
        /// Constructor.
        /// </summary>
        public ChannelControl()
        {
            InitializeComponent();
        }

        /// <summary>
        /// Initialize the control with a set of channels.
        /// </summary>
        public virtual void Initialize(IEnumerable<NotificationChannel> channels)
        {           
            Debug.Assert(channels != null, "channels");
            Debug.Assert(Channels.Count == 0, "Already initialized");

            foreach (NotificationChannel channel in channels)
            {
                NotificationChannelUI uiChannel = new NotificationChannelUI(channel, this);
                Channels.Add(uiChannel);
            }

            UpdateAllEnabled(); // Initialize "enable all" checkbox for the channels.

            // Hook up to CollectionChanged to receive notifications of changes to the channel
            // list.  Do this after initializing the list as we don't want to flag the list as changed yet.
            Channels.CollectionChanged += Channels_CollectionChanged;
        }

        /// <summary>
        /// The collection of channels.
        /// </summary>
        internal NotificationChannelUICollection Channels
        {
            get
            {
                Debug.Assert(channelsListBox != null);
                Debug.Assert(channelsListBox.ItemsSource is NotificationChannelUICollection);

                return channelsListBox.ItemsSource as NotificationChannelUICollection;
            }
        }

        /// <summary>
        /// Handler for when the channels collection has been modified so we can notify the 
        /// owner of the control to save the updated collection.
        /// </summary>
        private void Channels_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            FirePropertyChanged("Channels");
            UpdateAllEnabled(); // Update the "All enabled" checkbox if a channel is added/removed.
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// The property changed event to notify the owner if the channels collection
        /// has been modified (and needs to be saved).
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Fires the PropertyChanged event.
        /// </summary>
        /// <param name="name">Name of the property that has changed.</param>
        internal void FirePropertyChanged(string name)
        {
            Debug.Assert(!String.IsNullOrEmpty(name), "name");

            if (PropertyChanged == null)
            {
                return;
            }

            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }

        #endregion

        /// <summary>
        /// The currently selected channel.
        /// </summary>
        private NotificationChannelUI SelectedChannel
        {
            get
            {
                Debug.Assert(channelsListBox != null, "channelsListBox");

                return channelsListBox.SelectedIndex >= 0 ? Channels[channelsListBox.SelectedIndex] : null;
            }
        }

        /// <summary>
        /// The index of the currently selected channel.
        /// </summary>
        private int SelectedIndex
        {
            get
            {
                Debug.Assert(channelsListBox != null, "channelsListBox");

                return channelsListBox.SelectedIndex;
            }
        }

        /// <summary>
        /// True if all channels are enabled; false otherwise.  This property has a two-way
        /// binding with the "enable all" checkbox.
        /// </summary>
        public bool AllEnabled
        {
            get
            {
                int validChannelCount = 0; // Counts channels which have an active checkbox.

                foreach (NotificationChannelUI channel in Channels)
                {
                    if (channel.CheckBoxIsEnabled)
                    {
                        if (!channel.IsEnabled)
                        {
                            return false;
                        }
                        ++validChannelCount;
                    }
                }

                return validChannelCount > 0;
            }
            set // User has checked/unchecked the "enable all" checkbox.
            {
                foreach (NotificationChannelUI channel in Channels)
                {
                    if (!channel.CheckBoxIsEnabled)
                    {
                        continue; // Ignore if the checkbox is not enabled.
                    }
                    channel.IsEnabled = value;
                }
            }
        }

        /// <summary>
        /// Called when an individual channel checkbox is changed or if the channels list
        /// is updated in order to update the value of the "enable all" checkbox to match
        /// the status of the individual checkboxes.
        /// </summary>
        internal void UpdateAllEnabled()
        {
            if (enableAll.IsChecked.Value && !AllEnabled ||
                !enableAll.IsChecked.Value && AllEnabled)
            {
                // Notify WPF to update the checkbox to the value of the AllEnabled property.
                FirePropertyChanged("AllEnabled"); 
            }
        } 

        /// <summary>
        /// Called by WPF to determine whether to enable the "Add" button or not.
        /// </summary>
        private void AddCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            Debug.Assert(sender != null, "sender");
            Debug.Assert(e != null, "e");

            e.CanExecute = true;
        }
        
        /// <summary>
        /// Command handler for the "Add" button.
        /// </summary>
        private void AddChannel(object sender, ExecutedRoutedEventArgs e)
        {
            Debug.Assert(sender != null, "sender");
            Debug.Assert(e != null, "e");

            // Add a new channel to be inserted at channelIndex.
            AddEditChannel(/*editChannel*/null, /*channelIndex*/Channels.Count);
        }

        /// <summary>
        /// Test whether the "Edit" button should be enabled.
        /// </summary>
        protected virtual void EditCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            Debug.Assert(sender != null, "sender");
            Debug.Assert(e != null, "e");

            e.CanExecute = channelsListBox != null && channelsListBox.SelectedIndex >= 0;
        }

        /// <summary>
        /// Command handler for the "Edit" button.
        /// </summary>
        private void EditChannel(object sender, ExecutedRoutedEventArgs e)
        {
            Debug.Assert(sender != null, "sender");
            Debug.Assert(e != null, "e");

            AddEditChannel(SelectedChannel.Channel, SelectedIndex);
        }

        /// <summary>
        /// Invokes the add/edit channel dialog.
        /// </summary>
        /// <param name="editChannel">The channel to edit or null to add a new channel.</param>
        /// <param name="channelIndex">The index of the resulting channel.</param>
        private void AddEditChannel(NotificationChannel editChannel, int channelIndex)
        {
            // Invoke the add/edit dialog with a copy (so that it can be discarded if the
            // user cancels) of the channel if editing.
            AddEditChannelDialog addEditDialog = 
                new AddEditChannelDialog(editChannel != null ? editChannel.Copy() : null);

            if (!addEditDialog.ShowDialog().Value)
            {
                return; // cancelled
            }

            InnerAddEditChannel(editChannel != null, channelIndex, addEditDialog.Channel, addEditDialog.AddToTop);
        }

        /// <summary>
        /// Moves a newly added or edited channel to its appropriate place in the channel list.
        /// </summary>
        /// <param name="editingChannel">True if editing a channel.</param>
        /// <param name="channelIndex">The channel index.</param>
        /// <param name="updatedChannel">The newly created or edited channel.</param>
        /// <param name="addToTop">True if the channel should appear at the top of the list.</param>
        private void InnerAddEditChannel(bool editingChannel, int channelIndex, NotificationChannel updatedChannel, bool addToTop)
        {
            Debug.Assert(updatedChannel != null, "updatedChannel");

            NotificationChannelUI uiChannel = new NotificationChannelUI(updatedChannel, this);

            if (editingChannel)
            {
                Channels[channelIndex] = uiChannel;
                if (addToTop)
                {
                    Channels.Move(channelIndex, 0);
                }
            }
            else
            {
                // Insert the channel at the appropriate index (Channels.Count is allowed).
                Channels.Insert(addToTop ? 0 : channelIndex, uiChannel);
            }

            uiChannel.RefreshCheckBoxIsEnabled(); // Force a refresh of EnableCheckBox property.
        }

        /// <summary>
        /// Test whether the "Delete" button should be enabled.
        /// </summary>
        private void DeleteCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            Debug.Assert(sender != null, "sender");
            Debug.Assert(e != null, "e");

            e.CanExecute = channelsListBox != null &&
                           channelsListBox.SelectedIndex >= 0 &&
                           SelectedChannel.Channel is NotificationCustomChannel;
        }

        /// <summary>
        /// Delete the selected channel.
        /// </summary>
        private void DeleteChannel(object sender, ExecutedRoutedEventArgs e)
        {
            Debug.Assert(sender != null, "sender");
            Debug.Assert(e != null, "e");

            DeleteChannel(!NotificationChannel.DoNotPromptUserOnDelete);
        }

        /// <summary>
        /// Delete the selected channel, optionally warning the user first.
        /// </summary>
        /// <param name="warnUserBeforeDeleting">True to display a warning dialog; false otherwise.</param>
        private void DeleteChannel(bool warnUserBeforeDeleting)
        {
            if (warnUserBeforeDeleting)
            {
                MessageBoxResult result =
                    MessageBox.Show(Properties.Resources.ChannelControlDeleteWarning,
                                    Properties.Resources.ChannelControlWarningTitle,
                                    MessageBoxButton.YesNo,
                                    MessageBoxImage.Warning,
                                    MessageBoxResult.Yes,
                                    MessageBoxOptions.None);

                if (result == MessageBoxResult.No)
                {
                    return;
                }
            }

            Channels.Remove(SelectedChannel);
        }

        /// <summary>
        /// Determines whether the "Up" button should be enabled.
        /// </summary>
        private void UpCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            Debug.Assert(sender != null, "sender");
            Debug.Assert(e != null, "e");

            e.CanExecute = channelsListBox != null && channelsListBox.SelectedIndex > 0;
        }

        /// <summary>
        /// Handler for the "Up" button.  Moves the selected channel up one position.
        /// </summary>
        private void MoveChannelUp(object sender, ExecutedRoutedEventArgs e)
        {
            Debug.Assert(sender != null, "sender");
            Debug.Assert(e != null, "e");

            NotificationChannelUI channel = SelectedChannel;

            int selectedIndex = SelectedIndex; // SelectedIndex changes when we move selected channel up

            Channels[selectedIndex] = Channels[selectedIndex - 1];
            Channels[selectedIndex - 1] = channel;

            channelsListBox.SelectedIndex = selectedIndex - 1;
            channelsListBox.ScrollIntoView(channel);
        }

        /// <summary>
        /// Determines whether the "Down" button should be enabled.
        /// </summary>
        private void DownCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            Debug.Assert(sender != null, "sender");
            Debug.Assert(e != null, "e");

            e.CanExecute = channelsListBox != null && channelsListBox.SelectedIndex >= 0 && channelsListBox.SelectedIndex < channelsListBox.Items.Count - 1;
        }

        /// <summary>
        /// Handler for the "Down" button.  Moves the selected channel down one position.
        /// </summary>
        private void MoveChannelDown(object sender, ExecutedRoutedEventArgs e)
        {
            Debug.Assert(sender != null, "sender");
            Debug.Assert(e != null, "e");

            NotificationChannelUI channel = SelectedChannel;

            int selectedIndex = SelectedIndex;

            Channels[selectedIndex] = Channels[selectedIndex + 1];
            Channels[selectedIndex + 1] = channel;

            channelsListBox.SelectedIndex = selectedIndex + 1;
            channelsListBox.ScrollIntoView(channel);
        }

        /// <summary>
        /// Handler for double-clicking on the listbox to edit an item.
        /// </summary>
        private void channelsListBox_MouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            Debug.Assert(sender != null, "sender");
            Debug.Assert(e != null, "e");

            if (!EditCommand.CanExecute(sender, editButton))
            {
                return; // Double-clicking an item only valid if editing is allowed.
            }

            // Find the ListBoxItem containing the item that was clicked on, ignoring if the double-click
            // originated from the checkbox.
            ListBoxItem item = 
                WPFHelpers.GetAncestorItemFromObject<ListBoxItem>(
                    e.OriginalSource, typeof(CheckBox), /*suppressAssertion*/true
                    );
            
            if (item == null) 
            {
                return; // Either an item checkbox or a region with no list entries was clicked, ignore.
            }

            NotificationChannelUI channel = item.DataContext as NotificationChannelUI;

            Debug.Assert(channel != null, "channel");

            AddEditChannel(channel.Channel, Channels.IndexOf(channel));
        }
    } 
}
